Two Million Threats
Assurance and security in the Node.JS ecosystem
All it took to bring down millions of Node.js applications was one click.
In March 2016, Azer Koçulu - a Node.js developer - pulled down more than 250 of his published modules from the npm JavaScript repository. Included in the un-published modules was a seemingly insignificant package, 11 lines long, called “left-pad” which adjusts how text is aligned.
Like many open source development ecosystems, node.js relies of the extensive reuse of code to expedite development. In the giant npm repository, which includes more than 700,000 separate components, code packages often call on other packages in order to function as designed. And these dependencies change frequently: Together, there were 2,029,645 updates in 2017 to these packages. This interdependency is both hugely beneficial and also a source of fragility.
And, unbeknownst to most developers, left-pad has been reused by many, many node packages, often indirectly by packages that developers depend on. When left-pad was deleted, every downstream package which relied upon it directly or indirectly failed to install - leaving thousands with dead applications. All because one developer pushed one button.
Although the damage was reversed, and left-pad restored to the npm, the message was clear: modularity and reuse are powerful tools for developers, but they can leave ecosystems, like node, vulnerable.
This is a problem that ISR’s Christian Kästner is taking on. “We’re not saying that package authors are intentionally malicious,” he notes, “only that they could be and that there are significant security concerns given the size of the attack surface and low levels of quality assurance.”
The significance of these security concerns stems, in part, due to node’s deep reliance on dependencies. Most packages on npm require at least one other package in order to function, with many requiring more. This branching network of packages presents a massive number of possible points where an attack may gain access. And, as Kästner notes, “due to the flexibility of [JavaScript] and the limited security controls in Node, a lot of damage can be done once an attacker gains access to any of the accounts controlling those packages.”
So how do you secure an ecosystem with millions of line of code across hundreds of thousands of packages? Moreover, how do you secure this environment without stifling the free sharing and exchange of resources that has made these distributed modes of production so powerful? Kästner believes that the right approach is to focus initially on the smallest, most innocuous packages - like “left-pad” - which larger, more complex packages rely upon.
Many of these small packages perform very simple functions that do not require access to the internet, or file system privileges. “For the simple things, like Left Pad and many utilities, that lots of people are using, we can make sure that we don’t make it exploitable easily,” Kästner explains. “Even if somebody tries to do something malicious with those packages, it would be very difficult to gain access to or send data anywhere. As soon as a package which previously didn’t require any outside access attempted to connect to internet, access the file system, or connect to another module, you’d be notified of the change and the package would be flagged.”
But how would Kästner’s system know which packages required access permissions? Simple: A manifest. Many applications - for example, apps in the GooglePlay store - are required to submit a permissions manifest when they are uploaded. Kästner proposes that, in component repositories such as npm, permissions can be inferred from the code. These suggested permissions would then be provided to developers to confirm or amend prior to submitting their package to the repository. Developers who decide to depend on a package can then review whether the permissions the package requests make sense and would be notified if those permissions should change.
While these may seem like obvious or simple fixes to a very grand technical problem, the beauty is in its simplicity, Kästner believes. “Node isn’t the only ecosystem that is experiencing this threat from malicious package updates. Python, Ruby, and others are almost as reliant of modular and reusable code. Since approximately 32% of the packages on Node are simple in nature and require very few access permissions, the hope is that we can show that with a few relatively minor changes to the ecosystem supply-chain, you can greatly reduce the overall attack surface and make the entire ecosystem more secure.”
And, as recent news suggests, the need is greater than ever. Kästner, in particular, points to an example from the summer of 2018 wherein a hacker gained access to a developer's npm account and injected malicious code into a popular JavaScript library, code that was designed to steal the npm credentials of users who utilize the poisoned package inside their projects. “There is a strong likelihood that our work here could have prevented just such an attack.”